#!/usr/bin/perl -w
#
# Copyright (C) 2007, 2008, 2009, 2010 Apple Inc.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict;
use warnings;

use File::Basename;
use File::Find;
use File::Temp qw(tempfile);
use Getopt::Long;

sub preprocessorFlags();
sub preprocessorMacros($\@);
sub process($$$);

my $showHelp = 0;
my $verbose = 1;

my $result = GetOptions(
    "help"        => \$showHelp,
    "verbose"     => \$verbose,
);
if (!$result || $showHelp || scalar(@ARGV) != 2) {
    print STDERR basename($0) . " [-h|--help] [-v|--verbose] infile outfile\n";
    exit 1;
}

my $inFile = shift @ARGV;
my $outFile = shift @ARGV;

my $arch;
if ($outFile =~ /\.([^.]+)\.exp$/) {
    $arch = $1;
}
die "Unknown arch!" if !defined($arch) || !$arch;

print "Processing $inFile for $arch...\n" if $verbose;
process($inFile, $outFile, $arch);

exit 0;

sub preprocessorFlags()
{
    my @features = split(/\s+/, $ENV{GCC_PREPROCESSOR_DEFINITIONS});
    return @features;
}

sub preprocessorMacros($\@)
{
    my ($cc, $cc_args) = @_;

    my @args = @$cc_args;

    push(@args, map { "-D" . $_ } preprocessorFlags());

    push(@args, qw(-include wtf/Platform.h -include wtf/Assertions.h));
    # Set framework search paths to find <JavaScriptCore/Platform.h>.  Without these, cc
    # finds /System/Library/Frameworks/JavaScriptCore.framework/PrivateHeaders/Platform.h
    # or complains if you don't have that installed.  We search BUILT_PRODUCTS_DIR first
    # for local developer builds, then the installation path for B&I builds.
    push(@args, "-F" . $ENV{BUILT_PRODUCTS_DIR}) if $ENV{BUILT_PRODUCTS_DIR};
    push(@args, "-I" . $ENV{BUILT_PRODUCTS_DIR} . "/usr/local/include") if $ENV{BUILT_PRODUCTS_DIR};
    push(@args, "-I" . $ENV{BUILT_PRODUCTS_DIR} . $ENV{SDKROOT} . "/usr/local/include") if $ENV{BUILT_PRODUCTS_DIR};
    # Xcode 3.1 is required for SDKROOT to be set.
    if (exists $ENV{SYSTEM_LIBRARY_DIR} && $ENV{SYSTEM_LIBRARY_DIR}) {
        my $frameworkSearchPath = $ENV{SDKROOT} . $ENV{SYSTEM_LIBRARY_DIR};
        if ($ENV{PLATFORM_NAME} eq "iphoneos" || $ENV{PLATFORM_NAME} eq "iphonesimulator") {
            $frameworkSearchPath .= "/PrivateFrameworks";
        } else {
            $frameworkSearchPath .= "/Frameworks";
        }
        push(@args, "-F" . $frameworkSearchPath);
    }

    push(@args, "-I" . $ENV{BUILT_PRODUCTS_DIR} . $ENV{SDKROOT} . "/usr/local/include") if $ENV{BUILT_PRODUCTS_DIR};
    push(@args, "-I" . $ENV{SDKROOT} . "/usr/local/include");

    chomp(my $sdk_version = `xcrun --sdk $ENV{SDKROOT} --show-sdk-version`);
    if ($ENV{PLATFORM_NAME} eq "iphoneos") {
        push(@args, "-miphoneos-version-min=" . $sdk_version);
    }
    if ($ENV{PLATFORM_NAME} eq "iphonesimulator") {
        push(@args, "-mios-simulator-version-min=" . $sdk_version);
    }

    # Print out #define lines for all macros.
    push(@args, qw(-dM /dev/null));

    print join(" ", $cc, @args), "\n" if $verbose;
    local $/ = undef;
    open(CC, "-|", $cc, @args) or die "$!";
    my $macros = <CC>;
    close(CC);

    return $macros;
}

sub process($$$)
{
    my ($inFile, $outFile, $arch) = @_;

    if (!exists $ENV{SDKROOT} or !$ENV{SDKROOT}) {
        die "SDKROOT environment variable not found";
    }

    chomp (my $cc = `xcrun -find cc`);

    # Run cpp (-E) but don't print the line number info (-P).
    my @args = qw(-E -P);

    # Provide a default source type since *.in isn't a known file extension.
    push(@args, qw(-std=gnu++11 -x c++));

    # Make sure cc knows which SDK we're using.
    push(@args, "-isysroot", $ENV{SDKROOT});

    push(@args, "-arch", $arch);

    my $macros = preprocessorMacros($cc, @args);

    push(@args, "-o", $outFile);
    push(@args, "-");

    print join(" ", $cc, @args), "\n" if $verbose;
    open(CC, "|-", $cc, @args) or die "$!";
    open(IN, "<", $inFile) or die "$!";

    print CC $macros;
    while (my $line = <IN>) {
        next if ($line =~ /^#/ && $line !~ /^#\s*(if|ifdef|ifndef|else|elif|endif)/);
        print CC $line;
    }

    close(IN);
    close(CC);

    my $fileCount = chmod 0644, $outFile;
    if ($fileCount != 1) {
        die "Could not chmod 0644 $outFile: $!";
    }
}

